import { atom, getDefaultStore, useAtomValue } from "jotai"
import { atomWithDefault, atomWithStorage } from "jotai/utils"
import _ from "lodash"
import { createSyncStorage, genStorageKey } from "./common"
const store = getDefaultStore()

/**
 * 創建一個帶有資料獲取功能的 Repository Atom
 * @template Props - API 請求參數的類型
 * @template Result - API 響應數據的類型
 * @param read - 資料獲取函數，返回 Promise
 * @param options - 配置選項
 * @param options.defaultProps - 默認的請求參數
 * @param options.autoFetch - 是否在創建時自動獲取數據
 */
export function atomRepositoryWithFetcher<Props extends object, Result extends object>(
    read: (props?: Props) => Promise<Result>,
    options?: {
        // 默认参数
        defaultProps?: NonNullable<Props>
        // 一創建就先 fetch 一次
        autoFetch?: boolean
    },
) {
    // 獲取 jotai 的默認 store
    const store = getDefaultStore()

    // 暫存數據，用於避免重複請求
    let tmp_data: NonNullable<Result>

    // 創建原子狀態
    const _t = atom(Date.now()) // 時間戳，用於觸發更新
    const _loading = atom(false) // 加載狀態
    const _fetched = atom(false) // 是否已獲取數據

    // 創建防抖的數據獲取函數
    const readDebounced = _.debounce(read.bind(null, options?.defaultProps), 1000, {
        leading: true, // 第一次調用立即執行
        trailing: false, // 不在結束時執行
    })

    const firstFetch = async () => {
        try {
            store.set(_loading, true)
            tmp_data = await readDebounced()
            store.set(_fetched, true)
        } finally {
            store.set(_loading, false)
        }
        return tmp_data
    }

    const firstFetchDebounced = _.debounce(firstFetch, 1000, {
        leading: true, // 第一次調用立即執行
        trailing: false, // 不在結束時執行
    })

    /**
     * 首次獲取數據的 atom
     * 使用 atomWithDefault 確保只在首次訪問時執行
     */
    const _dataFirstFetchAtom = atomWithDefault(firstFetchDebounced)

    /**
     * 主數據 atom
     * 讀取器：根據 fetched 狀態決定是否需要首次獲取
     * 寫入器：執行數據更新操作
     */
    const _data = atom(async (get) => {
        const fetched = get(_fetched)
        get(_t) // 訂閱時間戳以觸發更新
        if (!fetched) {
            return (await get(_dataFirstFetchAtom)) as Result
        }
        return tmp_data
    })

    const _reload = async (props?: Props) => {
        try {
            store.set(_loading, true)
            tmp_data = await read(props)
            store.set(_t, Date.now()) // 更新時間戳觸發重渲染
        } finally {
            store.set(_loading, false)
        }
    }

    // 返回所有相關的 atoms 和方法
    return {
        atoms: {
            data: _data,
            loading: atom((get) => get(_loading)),
            fetched: atom((get) => get(_fetched)),
        },
        reload: _reload,
    }
}

/**
 * Repository Hook
 * 提供簡潔的接口來使用 Repository
 * @template P - 請求參數類型
 * @template R - 響應數據類型
 * @param repo - Repository 實例
 * @returns 包含數據、加載狀態、是否已獲取和重新加載方法的對象
 */
export function useRepository<P extends object, R extends object>(
    repo: ReturnType<typeof atomRepositoryWithFetcher<P, R>>,
) {
    const data = useAtomValue(repo.atoms.data) // 數據和重載方法
    const loading = useAtomValue(repo.atoms.loading) // 加載狀態
    const fetched = useAtomValue(repo.atoms.fetched) // 是否已獲取

    return {
        data, // 數據
        loading, // 加載狀態
        fetched, // 是否已獲取
        reload: repo.reload, // 重新加載方法
    }
}

export function atomWithCached<T>(
    key: string,
    opts?: { init?: () => Promise<T>; initValue?: T; storage?: Storage },
) {
    const { init, initValue, storage } = opts ?? {}
    const _atom = atomWithStorage<{ data?: T; _t: number }>(
        //
        genStorageKey(key),
        {
            data: initValue,
            _t: 0,
        },
        createSyncStorage(storage),
        { getOnInit: true },
    )

    if (init) {
        init().then((data) => {
            store.set(_atom, {
                data,
                _t: Date.now(),
            })
        })
    }

    return _atom
}

export const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
